namespace CloudNinja.Metering.Providers
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using CloudNinja.Metering.Common.Logging;

    using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;
    using Microsoft.WindowsAzure.Storage;
    using Microsoft.WindowsAzure.Storage.Table;

    public class AzureStorageParserBookmarkRepository : IParserBookmarkRepository
    {
        private const string StateTableName = "ParserBookmark";

        private readonly CloudStorageAccount storageAccount;

        private readonly ILogger logger;

        private readonly CloudTable table;

        private readonly RetryPolicy<StorageTransientErrorDetectionStrategy> retryPolicy;

        public AzureStorageParserBookmarkRepository(string connectionString, ILogger logger)
        {
            if (!CloudStorageAccount.TryParse(connectionString, out this.storageAccount))
            {
                throw new ArgumentException(string.Format("Connection string {0} is not valid.", connectionString));
            }

            this.logger = logger;
            var tableClient = this.storageAccount.CreateCloudTableClient();
            this.table = tableClient.GetTableReference(StateTableName);

            if (!this.table.Exists())
            {
                this.table.Create();
                var dummyEntity = new ParserBookmark("void", "void", DateTime.Now, 0);
                this.table.Execute(TableOperation.Insert(dummyEntity));
                this.table.Execute(TableOperation.Delete(dummyEntity));
            }

            var retryStrategy = new Incremental(3, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));

            this.retryPolicy = new RetryPolicy<StorageTransientErrorDetectionStrategy>(retryStrategy);
        }

        public ParserBookmark GetBookmark(string logSet, string logName)
        {
            this.retryPolicy.Retrying +=
                (sender, args) =>
                this.logger.LogInformation(
                    string.Format("Retrying in {0} seconds after exception {1}", args.Delay, args.LastException));

            IEnumerable<ParserBookmark> parseState = null;

            try
            {
                this.retryPolicy.ExecuteAction(
                    () =>
                        {
                            var query =
                                new TableQuery<ParserBookmark>().Where(
                                    TableQuery.CombineFilters(
                                        TableQuery.GenerateFilterCondition(
                                            "PartitionKey",
                                            QueryComparisons.Equal,
                                            logSet),
                                        TableOperators.And,
                                        TableQuery.CombineFilters(
                                            TableQuery.GenerateFilterCondition("LogSet", QueryComparisons.Equal, logSet),
                                            TableOperators.And,
                                            TableQuery.GenerateFilterCondition(
                                                "LogName",
                                                QueryComparisons.Equal,
                                                logName))));
                            parseState = this.table.ExecuteQuery(query);
                    });
            }
            catch (Exception)
            {
                this.logger.LogError("All retries failed for log parsing GetBookmark.");
            }

            return parseState == null ? null : parseState.ToList().OrderByDescending(b => b.LastTimeParsed).FirstOrDefault();
        }

        public void SetBookmark(ParserBookmark bookmark)
        {
            this.retryPolicy.Retrying +=
               (sender, args) =>
               this.logger.LogInformation(
                   string.Format("Retrying in {0} seconds after exception {1}", args.Delay, args.LastException));

            try
            {
                this.retryPolicy.ExecuteAction(
                    () =>
                    {
                        this.table.Execute(TableOperation.Insert(bookmark));
                    });
            }
            catch (Exception)
            {
                this.logger.LogError("All retries failed for saving the bookmark.");
            }
        }
    }
}